import Axios, {
  type AxiosInstance,
  type AxiosRequestConfig,
  type CustomParamsSerializer,
  type AxiosResponse
} from "axios";
import type {
  PureHttpError,
  RequestMethods,
  PureHttpResponse,
  PureHttpRequestConfig
} from "./types.d";
import { stringify } from "qs";
import NProgress from "../progress";
import { getToken, formatToken, removeToken } from "@/utils/auth";
import { useUserStoreHook } from "@/store/modules/user";
import { ElMessage } from "element-plus";

// 定义后端响应数据类型
interface ApiResponse {
  status: string;
  msg?: string;
  message?: string;
  data?: any;
}

// 相关配置请参考：www.axios-js.com/zh-cn/docs/#axios-request-config-1
const defaultConfig: AxiosRequestConfig = {
  // 请求超时时间
  timeout: 10000,
  headers: {
    Accept: "application/json, text/plain, */*",
    "Content-Type": "application/json",
    "X-Requested-With": "XMLHttpRequest"
  },
  // 数组格式参数序列化（https://github.com/axios/axios/issues/5142）
  paramsSerializer: {
    serialize: stringify as unknown as CustomParamsSerializer
  }
};

// 显示二次认证弹窗
const showSecondaryAuth = () => {
  return new Promise((resolve, reject) => {
    // 计算窗口位置，使其居中显示
    const width = 500;
    const height = 650;
    const left = (window.screen.width - width) / 2;
    const top = (window.screen.height - height) / 2;

    // 打开新窗口
    const authWindow = window.open(
      "/#/SecondaryAuth",
      "SecondaryAuth",
      `width=${width},height=${height},left=${left},top=${top},menubar=no,toolbar=no,location=no,status=no`
    );

    if (!authWindow) {
      ElMessage.error("弹窗被浏览器拦截，请允许弹窗后重试");
      reject(new Error("弹窗被拦截"));
      return;
    }

    // 监听消息
    const messageHandler = (event: MessageEvent) => {
      if (event.origin !== window.location.origin) return;

      if (event.data.type === "auth-success") {
        window.removeEventListener("message", messageHandler);
        ElMessage.success(event.data.msg || "解除限制成功！");
        resolve(event.data);
      } else if (event.data.type === "auth-cancel") {
        window.removeEventListener("message", messageHandler);
        reject(new Error("用户取消认证"));
      }
    };

    window.addEventListener("message", messageHandler);

    // 监听窗口关闭
    const checkClosed = setInterval(() => {
      if (authWindow.closed) {
        clearInterval(checkClosed);
        window.removeEventListener("message", messageHandler);
        reject(new Error("认证窗口被关闭"));
      }
    }, 500);
  });
};

// 检测是否为微信环境
const isWechatBrowser = () => {
  const ua = navigator.userAgent.toLowerCase();
  return ua.indexOf("micromessenger") !== -1;
};

class PureHttp {
  constructor() {
    this.httpInterceptorsRequest();
    this.httpInterceptorsResponse();
  }

  /** `token`过期后，暂存待执行的请求 */
  private static requests = [];

  /** 防止重复刷新`token` */
  private static isRefreshing = false;

  /** 初始化配置对象 */
  private static initConfig: PureHttpRequestConfig = {};

  /** 保存当前`Axios`实例对象 */
  private static axiosInstance: AxiosInstance = Axios.create(defaultConfig);

  /** 重连原始请求 */
  private static retryOriginalRequest(config: PureHttpRequestConfig) {
    return new Promise(resolve => {
      PureHttp.requests.push((token: string) => {
        config.headers["Authorization"] = formatToken(token);
        resolve(config);
      });
    });
  }

  /** 请求拦截 */
  private httpInterceptorsRequest(): void {
    PureHttp.axiosInstance.interceptors.request.use(
      async (config: PureHttpRequestConfig): Promise<any> => {
        // 开启进度条动画
        NProgress.start();
        // 优先判断post/get等方法是否传入回调，否则执行初始化设置等回调
        if (typeof config.beforeRequestCallback === "function") {
          config.beforeRequestCallback(config);
          return config;
        }
        if (PureHttp.initConfig.beforeRequestCallback) {
          PureHttp.initConfig.beforeRequestCallback(config);
          return config;
        }
        /** 请求白名单，放置一些不需要`token`的接口（通过设置请求白名单，防止`token`过期后再请求造成的死循环问题） */
        const whiteList = ["/refresh-token", "/login"];
        return whiteList.some(url => config.url.endsWith(url))
          ? config
          : new Promise(resolve => {
              const data = getToken();
              if (data) {
                const now = new Date().getTime();
                const expired = parseInt(data.expires) - now <= 0;
                if (expired) {
                  if (!PureHttp.isRefreshing) {
                    PureHttp.isRefreshing = true;
                    // token过期刷新
                    useUserStoreHook()
                      .handRefreshToken({ refreshToken: data.refreshToken })
                      .then(res => {
                        const token = res.data.accessToken;
                        config.headers["Authorization"] = formatToken(token);
                        PureHttp.requests.forEach(cb => cb(token));
                        PureHttp.requests = [];
                      })
                      .finally(() => {
                        PureHttp.isRefreshing = false;
                      });
                  }
                  resolve(PureHttp.retryOriginalRequest(config));
                } else {
                  config.headers["Authorization"] = formatToken(
                    data.accessToken
                  );
                  resolve(config);
                }
              } else {
                resolve(config);
              }
            });
      },
      error => {
        return Promise.reject(error);
      }
    );
  }

  /** 响应拦截 */
  private httpInterceptorsResponse(): void {
    const instance = PureHttp.axiosInstance;
    instance.interceptors.response.use(
      (response: PureHttpResponse) => {
        const $config = response.config;
        // 关闭进度条动画
        NProgress.done();
        // 优先判断post/get等方法是否传入回调，否则执行初始化设置等回调
        if (typeof $config.beforeResponseCallback === "function") {
          $config.beforeResponseCallback(response);
          return response.data;
        }
        if (PureHttp.initConfig.beforeResponseCallback) {
          PureHttp.initConfig.beforeResponseCallback(response);
          return response.data;
        }
        return response.data;
      },
      async (error: PureHttpError) => {
        const $error = error;
        $error.isCancelRequest = Axios.isCancel($error);
        // 关闭进度条动画
        NProgress.done();

        // 处理错误信息
        let errorMsg = "服务器异常";
        if ($error.response) {
          const { status, data } =
            $error.response as AxiosResponse<ApiResponse>;

          // 处理402二次鉴权的情况
          if (status === 402) {
            // 判断是否为微信环境
            if (isWechatBrowser()) {
              ElMessage.error(
                "暂不支持微信环境下的二次认证，请使用其他浏览器访问"
              );
              return Promise.reject(new Error("不支持微信环境二次认证"));
            }

            try {
              // 显示二次认证弹窗
              await showSecondaryAuth();
              // 认证成功后重试原请求
              return instance.request($error.config);
            } catch (authError) {
              // 用户取消认证或认证失败
              console.error("二次认证失败:", authError);
              ElMessage.error("二次鉴权失败，操作禁止执行");
              return Promise.reject(new Error("二次鉴权失败，操作禁止执行"));
            }
          }

          // 处理401未授权的情况
          if (status === 401) {
            // 清除token
            removeToken();
            // 跳转到登录页
            window.location.href = "/#/login";
            return Promise.reject(new Error("登录已过期，请重新登录"));
          }

          // 优先使用后端返回的错误信息
          errorMsg = data?.msg || data?.message || errorMsg;
        } else if ($error.message) {
          // 使用axios本身的错误信息
          if ($error.message.includes("timeout")) {
            errorMsg = "请求超时";
          } else if ($error.message.includes("Network Error")) {
            errorMsg = "网络异常";
          } else {
            errorMsg = $error.message;
          }
        }

        // 显示错误提示
        ElMessage.error(errorMsg);

        // 所有的响应异常 区分来源为取消请求/非取消请求
        return Promise.reject({
          ...$error,
          msg: errorMsg
        });
      }
    );
  }

  /** 通用请求工具函数 */
  public request<T>(
    method: RequestMethods,
    url: string,
    param?: AxiosRequestConfig,
    axiosConfig?: PureHttpRequestConfig
  ): Promise<T> {
    const config = {
      method,
      url,
      ...param,
      ...axiosConfig
    } as PureHttpRequestConfig;

    // 单独处理自定义请求/响应回调
    return new Promise((resolve, reject) => {
      PureHttp.axiosInstance
        .request(config)
        .then((response: undefined) => {
          resolve(response);
        })
        .catch(error => {
          reject(error);
        });
    });
  }

  /** 单独抽离的`post`工具函数 */
  public post<T, P>(
    url: string,
    params?: AxiosRequestConfig<P>,
    config?: PureHttpRequestConfig
  ): Promise<T> {
    return this.request<T>("post", url, params, config);
  }

  /** 单独抽离的`get`工具函数 */
  public get<T, P>(
    url: string,
    params?: AxiosRequestConfig<P>,
    config?: PureHttpRequestConfig
  ): Promise<T> {
    return this.request<T>("get", url, params, config);
  }
}

export const http = new PureHttp();
